﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Tao.OpenGl;
using System.Drawing;
using System.IO;
using System.Drawing.Imaging;
using AvantGameMachine.DataManagement;
using AvantGameMachine.FileSystem;
using Tao.FreeType;

namespace AvantGameMachine.Graphics
{
    public class Texture : IDisposable
    {
        private static List<Texture> textures = new List<Texture>();
        private int textureID=-1;
        private Size2D size;

        internal Texture()
        {
            textures.Add(this);
        }

        public Texture(Size2D _size)
            :this()
        {
            StandardInitTexture();
            byte[] data = GetByteArrayFromSize(_size);
            data = ClearByteArray(_size, data);

            Gl.glTexImage2D(Gl.GL_TEXTURE_2D, Gl.GL_RGBA8, 4, _size.Width, _size.Height, 0, Gl.GL_RGBA, Gl.GL_UNSIGNED_BYTE, data);
            size = _size;
        }

        private int next_p2 ( int a )
        {
        	int rval=1;
        	while(rval<a) rval<<=1;
        	return rval;
        }

        public Texture(ref FT_Bitmap _bitmap)
            : this()
        {
            StandardInitTexture();
            int width = next_p2(_bitmap.width);
            int height = next_p2(_bitmap.rows);

            unsafe
            {
                byte[] expanded_data = new byte[2 * width * height];
                byte[] buffer = ConvertPointerToArray((byte*)_bitmap.buffer.ToPointer(),(uint)(_bitmap.width*_bitmap.rows));

                for (int y = 0; y < height; y++)
                {
                    for (int x = 0; x < width; x++)
                    {
                        expanded_data[2* ((y * width) + x)] = expanded_data[(2 * ((y * width) + x)) + 1] = 
                            (x >= _bitmap.width || y >= _bitmap.rows) ? byte.MinValue : buffer[(_bitmap.width * y) + x];
                    }
                }

                Gl.glTexImage2D(Gl.GL_TEXTURE_2D, 0, 4/*Gl.GL_RGB8*/, width, height, 0, Gl.GL_LUMINANCE_ALPHA, Gl.GL_UNSIGNED_BYTE, expanded_data);
            }
            size = new Size2D(width, height);
        }

        private unsafe byte[] ConvertPointerToArray(byte* _pointer,uint _length)
        {
            byte[] tmp = new byte[_length];
            byte* iterator = _pointer;

            for (uint i=0;i<_length;i++)
            {
                tmp[i] = *iterator;
                iterator++;
            }

            return tmp;

        }

        public Texture(byte[] _data)
            : this()
        {
            CreateTextureFromBytes(_data);
        }
 
        public Texture(string _fileName)
            : this()
        {
            byte[] bytes = File.ReadAllBytes(_fileName);
            CreateTextureFromBytes(bytes);
        }

        public Texture(Address _address)
            : this()
        {
            FileInformation info = (FileInformation)AGMPlatform.FSManager.GetFile(_address);
            CreateTextureFromBytes((byte[])info.GetData());
        }

        public Size2D Size
        {
            get { return size; }
        }

        public int TextureID
        {
            get { return textureID; }
            set { textureID = value; }
        }

        private void CreateTextureFromBytes(byte[] _data)
        {
            Stream s = new MemoryStream(_data);
            Bitmap[] textureImage;
            textureImage = new Bitmap[1];
            textureImage[0] = new Bitmap(s);

            textureImage[0].RotateFlip(RotateFlipType.RotateNoneFlipY);     // Flip The Bitmap Along The Y-Axis
            // Rectangle For Locking The Bitmap In Memory
            Rectangle rectangle = new Rectangle(0, 0, textureImage[0].Width, textureImage[0].Height);
            // Get The Bitmap's Pixel Data From The Locked Bitmap
            BitmapData bitmapData = textureImage[0].LockBits(rectangle, ImageLockMode.ReadOnly, PixelFormat.Format32bppRgb);

            // Typical Texture Generation Using Data From The Bitmap
            StandardInitTexture();

            Gl.glTexImage2D(Gl.GL_TEXTURE_2D, 0, Gl.GL_RGBA8, textureImage[0].Width, textureImage[0].Height, 0, Gl.GL_BGRA, Gl.GL_UNSIGNED_BYTE, bitmapData.Scan0);

            if (textureImage[0] != null)
            {                                   // If Texture Exists
                textureImage[0].UnlockBits(bitmapData);                     // Unlock The Pixel Data From Memory
                size = new Size2D(textureImage[0].Size);
                textureImage[0].Dispose();                                  // Dispose The Bitmap
            }
        }

        private byte[] GetByteArrayFromSize(Size2D _size)
        {
            return  new byte[_size.Height * _size.Width * 4];
        }

        internal byte[] ClearByteArray(Size2D _size,byte[] _data)
        {
            for (int y = 0; y < _size.Height; y++)
            {
                for (int x = 0; x < _size.Width; x++)
                {
                    for (int b = 0; b < 4; b++)
                    {
                        if (b == 3)
                            _data[((y * _size.Height) + x) * 4] = 255;
                        else
                            _data[((y * _size.Height) + x) * 4] = 0;
                    }
                }
            }
            return _data;
        }

        internal void StandardInitTexture()
        {
            Gl.glGenTextures(1, out textureID);
            Gl.glBindTexture(Gl.GL_TEXTURE_2D, textureID);

            Gl.glTexParameteri(Gl.GL_TEXTURE_2D, Gl.GL_TEXTURE_MIN_FILTER, Gl.GL_LINEAR);
            Gl.glTexParameteri(Gl.GL_TEXTURE_2D, Gl.GL_TEXTURE_MAG_FILTER, Gl.GL_LINEAR);
        }

        public void Dispose()
        {
            if (textureID > -1)
            {
                Gl.glDeleteTextures(1, ref textureID);
                textureID = -1;
            }
        }

        internal static void DisposeAll()
        {
            Texture[] txts = textures.ToArray();
            textures.Clear();

            foreach (Texture t in textures)
            {
                t.Dispose();
            }
        }
    }
}
